//
//  main.m
//  findbt
//
//  Created by Vicente Silvestre on 3/21/13.
//  Copyright (c) 2013 Vicente Silvestre All rights reserved.
//

#import <cocoa/cocoa.h>
#import <Foundation/Foundation.h>
#import <IOBluetooth/objc/IOBluetoothDevice.h>
#import <IOBluetooth/objc/IOBluetoothHostController.h>
#import <IOBluetooth/objc/IOBluetoothDeviceInquiry.h>
#import <IOBluetooth/IOBluetoothUtilities.h>
#include <string.h>

IOBluetoothDevice *device;

//
// Print Device Info
//
void printDeviceInfo(IOBluetoothDevice *device)
{
    bool disconnect = false;
    NSString *name = device.name;
    NSString *strAddress = IOBluetoothNSStringFromDeviceAddress([device getAddress]);
    if( ! [device isConnected] ) {
        [device openConnection]; // synchronous call w/ default timeout
        disconnect = true;
    }
    BluetoothHCIRSSIValue rssi = device.rawRSSI; // -127 to +20 dB (+127 dB means n/a)
    char *major="", *minor="", tmp[64];
    switch( [device getDeviceClassMajor] ) {
        case 0:
            major="misc";
            switch( [device getClassOfDevice]>>16) {
                case 1: major="gps"; break; // speculative
                case 2: major="networking"; break;
                case 4: major="rendering"; break;
                case 8: major="capturing"; break;
                case 16: major="obj transfer"; break;
                case 32: major="audio"; break;
                case 64: major="telephony"; break;
                case 128: major="information"; break;
            } break;
        case 1:
            major="computer";
            switch( [device getDeviceClassMinor]>>2) {
                case 0: major="macbook"; break; // speculative
                case 1: major="desktop"; break;
                case 2: major="server computer"; break;
                case 3: major="laptop"; break;
                case 4: major="clamshell pda"; break;
                case 5: major="palm computer"; break;
                case 6: major="wearable computer"; break;
                case 7: major="tablet"; break;
            } break;
        case 2:
            minor=" phone";
            switch( [device getDeviceClassMinor]>>2) {
                case 0: minor="phone"; break;
                case 1: major="cellular"; break;
                case 2: major="cordless"; break;
                case 3: major="smart"; break;
                case 4: major="modem"; break;
                case 5: major="isdn"; break;
            } break;
        case 3:
            major="network AP ";
            switch( [device getDeviceClassMinor]>>5) {
                case 0: major="100%"; break;
                case 1: major="1-17%"; break;
                case 2: major="17-33%"; break;
                case 3: major="33-50%"; break;
                case 4: major="50-67%"; break;
                case 5: major="67-83"; break;
                case 6: major="83-99%"; break;
                case 7: major="0%"; break;
            } break;
        case 4:
            major="audio/video";
            switch( [device getDeviceClassMinor]>>2) {
                case 0: major="headset w/ mic"; break; // speculative
                case 1: major="iOS device"; break; // speculative (should be 'audio headset')
                case 2: major="audio handsfree"; break;
                case 4: major="mic"; break;
                case 5: major="speaker"; break;
                case 6: major="headphones"; break;
                case 7: major="portable audio"; break;
                case 8: major="car audio"; break;
                case 9: major="set-top box"; break;
                case 10: major="hifi audio"; break;
                case 11: major="vcr"; break;
                case 12: major="vid cam"; break;
                case 13: major="camcorder"; break;
                case 14: major="vid monitor"; break;
                case 15: major="vid display/spkr"; break;
                case 16: major="vid conf"; break;
                case 18: major="game audio/vid"; break;
            } break;
        case 5:
            major="peripheral";
            switch( [device getDeviceClassMinor]>>6) {
                case 0: major="apple"; break; // speculative
                case 1: major="keyboard"; break;
                case 2: major="mouse"; break;
                case 3: major="keyboard/mouse"; break;
            }
            switch( ([device getDeviceClassMinor]>>2) & 0xf ) {
                case 1: minor=" joystick"; break;
                case 2: minor=" gamepad"; break;
                case 3: minor=" remote"; break;
                case 4: minor=" keyboard"; break; // speculative (apple keyboard, should be 'sensor')
                case 5: minor=" digitizer"; break;
                case 6: minor=" card-reader"; break;
                case 7: minor=" pen"; break;
                case 8: minor=" mouse"; break; // speculative (apple mouse, should be 'barcode scanner')
                case 9: minor=" wand"; break;
            } break;
        case 6:
            strcpy(tmp, "imaging");
            int min = [device getDeviceClassMinor]>>4;
            if( min & 1 ) strcat(tmp,"+display"); break;
            if( min & 2 ) strcat(tmp,"+camera"); break;
            if( min & 4 ) strcat(tmp,"+scanner"); break;
            if( min & 8 ) strcat(tmp,"+printer"); break;
            major=tmp;
            break;
        case 7:
            major="wearable";
            switch( [device getDeviceClassMinor]>>2) {
                case 1: major="wrist watch"; break;
                case 2: major="pager"; break;
                case 3: major="jacket"; break;
                case 4: major="helmet"; break;
                case 5: major="glasses"; break;
            } break;
        case 8:
            major="toy ";
            switch( [device getDeviceClassMinor]>>2) {
                case 1: minor="robot"; break;
                case 2: minor="vehicle"; break;
                case 3: minor="figure"; break;
                case 4: minor="controller"; break;
                case 5: minor="game"; break;
            } break;
        case 9:
            major="health ";
            switch( [device getDeviceClassMinor]>>2) {
                case 1: minor="bp monitor"; break;
                case 2: minor="thermometer"; break;
                case 3: minor="wheighing scale"; break;
                case 4: minor="glucose meter"; break;
                case 5: minor="pulse oximeter"; break;
                case 6: minor="heart rate monitor"; break;
                case 7: minor="data display"; break;
                case 8: minor="step counter"; break;
                case 9: minor="body comp analyzer"; break;
                case 10: minor="peak flow monitor"; break;
                case 11: minor="medication monitor"; break;
                case 12: minor="knee prosthesis"; break;
                case 13: minor="ankle prosthesis"; break;
                case 14: minor="manager"; break;
                case 15: minor="pers mobility"; break;
            } break;
        default:
            major="n/a";
            break;
    }
    if( 127 != rssi ) { // synchronous call with default timeout
        printf("%s %4d dB  %s (%s%s)\n", [strAddress UTF8String], rssi, [name UTF8String], major, minor);
    } else {
        printf("%s %4s dB  %s (%s%s)\n", [strAddress UTF8String], "?", [name UTF8String], major, minor);
    }
    if( disconnect ) {
        [device closeConnection];
    }
}

//
// Delegate Bluetooth Device Inquiry Protocol
//
@interface NSObject(IOBluetoothDeviceInquiry)
- (void) deviceInquiryComplete:(IOBluetoothDeviceInquiry *)sender error:(IOReturn)error aborted:(BOOL)aborted;
@end
@implementation NSObject(IOBluetoothDeviceInquiry)
- (void) deviceInquiryComplete:(IOBluetoothDeviceInquiry *)sender error:(IOReturn)error aborted:(BOOL)aborted {

    NSArray *devices = [sender foundDevices];
    for (IOBluetoothDevice *device in devices)
        printDeviceInfo(device);
    
    exit(0); // end program after listing all found devices
}
@end

//
// Syntax
//
void syntax(void)
{
    printf("\nfindbt 1.0 - Vicente Silvestre <vsilves@gmail.com>\n\n"
           "findbt\t\t\tPrints a list of nearby bluetooth devices\n"
           "findbt MAC_ADDRESS\tPrints \"yes\" or \"no\" to stdout\n\n");
    exit(1);
}

//
// Init
//
void init(void)
{
    IOBluetoothHostController *btController = [IOBluetoothHostController defaultController];
    if( [btController powerState] == 0 ) {
        printf("Bluetooth host controller is off\n");
        exit(1);
    }
}

//
// Prints a list of of nearby bluetooth devices to stdout
//
void scanBtDevices(void)
{
    init(); // quit if host bluetooth is off
    
    // List Paired Devices
    NSArray *devices = [IOBluetoothDevice pairedDevices];
    for (IOBluetoothDevice *device in devices)
        printDeviceInfo(device);
    
    // Scan for Non-Paired Devices
    id obj = [[NSObject alloc] init];
    IOBluetoothDeviceInquiry *inquirer = [IOBluetoothDeviceInquiry inquiryWithDelegate:obj];
    [inquirer setInquiryLength:8]; // lower scan timeout from 10 (default) to 8 secs
    [inquirer start];
    CFRunLoopRun(); // the delegate will exit(0) for us when done
}

//
// Prints yes/no to stdout if bluetooth device is nearby
//
void findBtDevice(NSString *macAddress)
{
    init(); // quit if host bluetooth is off
    
    device = [IOBluetoothDevice deviceWithAddressString:macAddress];
    if( ! device ) {
        printf("Improperly formatted MAC address\n");
        exit(1);
    }
    
    if( kIOReturnSuccess == [device remoteNameRequest:nil] )
        printf("yes\n");
    else
        printf("no\n");
}

//
// Main
//
// example: findbt 74:e1:b6:03:02:01
//
int main(int argc, const char * argv[])
{
    switch (argc) {
        case 1:
            scanBtDevices();
            break;
        case 2:
            if( ! strcmp("-h",argv[1]) || ! strcmp("--help",argv[1]) ) syntax();
            findBtDevice(@(argv[1]));
            break;
        default:
            syntax();
    }

    return 0;
}